<template>
  <div :class="['form-view', className]">
    <!-- 顶部工具栏 -->
    <div v-show="!isFormLoading || (isFormLoading && !hideFormWhenLoading)" v-if="showToolbar" class="form-view-toolbar">
      <slot name="form-view-toolbar">
        <Toolbar v-bind="{ ...toolbarProps, buttons: toolbarButtons }" @edit="onEdit" v-on="toolbarListeners">
          <!-- 工具栏左侧 -->
          <template #default="{ buttons }">
            <slot name="toolbar-button" :buttons="buttons"></slot>
          </template>
          <!-- 外部覆盖默认按钮 -->
          <template v-for="item in getToolbarSlotKeys()" #[item]>
            <slot :name="item"></slot>
          </template>
          <!-- 工具栏右侧 -->
          <template #toolbar-right>
            <slot name="toolbar-right"></slot>
            <ToolbarButton v-if="$parent.$options.flowForm && $parent.entity.id" icon="el-icon-data-analysis" label="流程跟踪" @click="showFlowProgress" />
          </template>
        </Toolbar>
      </slot>
    </div>
    <!-- 内容 -->
    <div v-show="!isFormLoading || (isFormLoading && !hideFormWhenLoading)" class="form-scroller-wrapper">
      <div ref="formScroller" :class="['form-scroller', 'absolute-box', contentClass]" @scroll="onFormScroll">
        <!-- 非表单 -->
        <div v-if="notForm" :style="{ width: formWidth }" class="not-form">
          <div v-if="title" class="form-view-title">{{ title }}</div>
          <!-- 表单内容 -->
          <slot>
            <EmptyView empty-tips="请添加表单内容" />
          </slot>
          <!-- 底部工具栏 -->
          <Toolbar v-if="footerBarButtons.length" v-bind="{ ...toolbarProps, buttons: footerBarButtons }" footer v-on="toolbarListeners">
            <!-- 默认插槽 -->
            <template #default="{ buttons, footer }">
              <slot name="footer-button" :buttons="buttons" :footer="footer"></slot>
            </template>
            <!-- 外部覆盖默认按钮 -->
            <template v-for="item in getFooterSlotKeys()" #[getFooterSlotKey(item)]>
              <slot :name="item"></slot>
            </template>
          </Toolbar>
          <!-- 表单底部 -->
          <slot v-if="$scopedSlots['form-bottom']" name="form-bottom"></slot>
        </div>
        <!-- 表单 -->
        <div v-else class="form-card" :style="{ width: `calc(${formWidth} + 40px)`, maxWidth: formMaxWidth }">
          <div class="form-content">
            <!-- 表单内容 -->
            <div v-if="title" class="form-view-title">
              {{ title }}
              <ElPopover v-if="subTitle" placement="bottom" width="300" :content="subTitle" trigger="hover">
                <template #reference>
                  <i class="el-icon-warning" style="font-size: 14px"></i>
                </template>
              </ElPopover>
            </div>
            <slot>
              <EmptyView empty-tips="请添加表单内容" />
            </slot>
            <!-- 底部工具栏 -->
            <Toolbar v-if="footerBarButtons.length" v-bind="{ ...toolbarProps, buttons: footerBarButtons }" footer v-on="toolbarListeners">
              <!-- 默认插槽 -->
              <template #default="{ buttons, footer }">
                <slot name="footer-button" :buttons="buttons" :footer="footer"></slot>
              </template>
              <!-- 外部覆盖默认按钮 -->
              <template v-for="item in getFooterSlotKeys()" #[getFooterSlotKey(item)]>
                <slot :name="item"></slot>
              </template>
            </Toolbar>
          </div>
        </div>
        <div
          v-if="$scopedSlots['form-bottom'] || ($parent.$options.flowForm && flowContext.logs.length)"
          class="form-card"
          :style="{ width: `calc(${formWidth} + 40px)`, maxWidth: formMaxWidth, minHeight: 0 }">
          <div class="form-content">
            <slot v-if="$parent.$options.flowForm" name="customize-flow-log" :logs="flowContext.logs">
              <FormItemGroup v-if="flowContext.logs.length" :gutter="0" block-title="办理意见">
                <FlowLog class="flow-log" :fetch="false" :show-title="false" :business-key="$parent.businessKey" />
              </FormItemGroup>
            </slot>
            <!-- 表单底部 -->
            <slot name="form-bottom"></slot>
          </div>
        </div>
      </div>
      <ResizeObserver @notify="onFormResize" />
    </div>
    <!-- 表单导航 -->
    <div v-if="formNav && !isFormLoading && showNav && groups.length" class="form-nav">
      <div class="form-nav-header">表单导航</div>
      <ul class="form-nav-list">
        <li
          v-for="(item, index) in groups"
          :key="item.blockTitle"
          :class="['form-nav-item', { active: activeIndex === index }]"
          @click="navItemClick(item, index)">
          {{ item.blockTitle }}
        </li>
      </ul>
    </div>
    <!-- 加载提示 -->
    <LoadingView v-if="isFormLoading" icon-size="34px" font-size="16px" />
    <ErrorView v-if="formLoadingStatus === -1" icon-size="34px" font-size="16px">
      <ElButton class="pageable-table-error" icon="table-status-icon el-icon-error" @click="reloadForm"> 加载失败，点击重试！ </ElButton>
    </ErrorView>
  </div>
</template>

<script>
  import { hasOwn, isInContainer, toStringArray, animate } from '@web-utils/core'
  import { merge } from '@web-utils/integrations/lodash'
  import FlowLog from '@/components/flowable/FlowLog.vue'
  import { flowProgressDialog } from '@/components/flowable'
  import { mapState } from 'vuex'

  const builtInButtons = [
    'back',
    'create',
    'save',
    'withdraw',
    'edit',
    'submit',
    'publish',
    'agree',
    'disagree',
    'reject',
    'finish',
    'cancel',
    'enable',
    'exp',
    'imp',
    'del',
    'disabled',
    'reload',
    'slot'
  ]

  const footerButtonSlots = builtInButtons.map(value => `${value}-bottom`)

  // 在顶部的按钮 返回,撤回,编辑
  const topButtons = ['back', 'withdraw', 'edit']
  // 在底部的按钮 保存,提交审批,发布,同意,不同意,完成,取消
  const bottomButtons = ['save', 'submit', '', 'publish', 'agree', 'disagree', 'reject', 'finish', 'cancel']

  const findParent = (el, selector) => {
    if (el.classList.contains(selector)) {
      return el
    }
    let parent = el.parentNode
    while (parent && !parent.classList.contains(selector)) {
      parent = parent.parentNode
    }
    return parent.classList.contains(selector) ? parent : null
  }

  export default {
    name: 'FormView',
    components: {
      FlowLog
    },
    inject: {
      flowContext: {
        default: {}
      }
    },
    props: {
      notForm: Boolean,
      loading: Boolean,
      formMaxWidth: {
        type: String,
        default: '1000px'
      },
      formWidth: {
        type: String,
        default: '75%'
      },
      toolbarAttrs: {
        type: Object,
        default: () => {}
      },
      toolbarListeners: Object,
      contentClass: String,
      className: String,
      title: String,
      subTitle: String,
      showToolbar: {
        type: Boolean,
        default: true
      },
      formNav: {
        type: Boolean,
        default: true
      },
      hideFormWhenLoading: {
        type: Boolean,
        default: true
      }
    },
    data() {
      return {
        groups: [],
        activeIndex: -1,
        tryByClick: false,
        showNav: false
      }
    },
    computed: {
      ...mapState('config', ['formButtonPosition']),
      topButtons() {
        if (this.formButtonPosition === 'top') {
          return [...topButtons, ...bottomButtons].filter(value => value !== 'cancel')
        } else {
          return topButtons
        }
      },
      bottomButtons() {
        if (this.formButtonPosition === 'bottom') {
          return bottomButtons
        } else {
          return []
        }
      },
      isFormLoading() {
        return this.loading || hasOwn(this.$parent, 'isFormLoaded') ? !this.$parent.isFormLoaded : false
      },
      formLoadingStatus() {
        return hasOwn(this.$parent, 'formLoadingStatus') ? this.$parent.formLoadingStatus : 0
      },
      toolbarProps() {
        const defaultProps = {}
        const buttons = this.toolbarAttrs?.buttons || this.$parent.buttons
        const toolbarAttrs = merge({}, this.toolbarAttrs, defaultProps)
        toolbarAttrs.buttons = buttons || ['back']
        toolbarAttrs.buttons = toStringArray(toolbarAttrs.buttons)
        return toolbarAttrs
      },
      toolbarButtons() {
        return this.toolbarProps.buttons
          .filter(value => {
            if (this.formButtonPosition === 'top') {
              return !this.bottomButtons.includes(value) && value.indexOf(':bottom') < 0 && value.indexOf('cancel') < 0
            }
            return !this.bottomButtons.includes(value) && value.indexOf(':bottom') < 0
          })
          .map(value => value.replace(':top', ''))
      },
      footerBarButtons() {
        let footerBarButtons = this.toolbarProps.buttons
          .filter(value => !this.topButtons.includes(value) && (this.bottomButtons.includes(value) || value.indexOf(':bottom') > -1))
          .map(value => value.replace(':bottom', ''))
        footerBarButtons = footerBarButtons || []
        if (this.formButtonPosition === 'bottom' && this.$parent.isEnabled && !footerBarButtons.includes('cancel')) {
          footerBarButtons.push('cancel')
        }
        return footerBarButtons
      }
    },
    created() {
      this.$on('FormView.addGroup', group => {
        if (group) {
          if (this.activeIndex === -1) {
            this.activeIndex = 0
          }
          this.groups.push(group)
          this.groups = this.groups.sort((aTab, bTab) => {
            const formCardA = findParent(aTab.$el, 'form-card')
            const formCardB = findParent(bTab.$el, 'form-card')
            if (formCardA === formCardB) {
              const aIndex = [].indexOf.call(aTab.$el.parentNode.children, aTab.$el)
              const bIndex = [].indexOf.call(bTab.$el.parentNode.children, bTab.$el)
              return aIndex - bIndex
            } else {
              const formScroller = findParent(formCardA, 'form-scroller')
              const aBlockIndex = [].indexOf.call(formScroller.children, formCardA)
              const bBlockIndex = [].indexOf.call(formScroller.children, formCardB)
              return aBlockIndex - bBlockIndex
            }
          })
          if (group.active) {
            this.activeIndex = this.groups.indexOf(group)
            this.navItemClick(group, this.activeIndex)
          }
        }
      })
      this.$on('FormView.removeGroup', tab => {
        if (tab) {
          const index = this.groups.indexOf(tab)
          this.groups.splice(index, 1)
          if (this.activeIndex > this.groups.length - 1) {
            this.activeIndex = 0
          }
        }
      })
      this.onFormResize({ width: window.document.body.clientWidth })
    },
    async activated() {
      if (this.$refs.formScroller) {
        await this.$nextTick()
        this.scrollTo(this.formScrollTop, this.formScrollLeft)
      }
    },
    methods: {
      getToolbarSlotKeys() {
        return Object.keys(this.$scopedSlots).filter(value => builtInButtons.includes(value))
      },
      getFooterSlotKeys() {
        return Object.keys(this.$scopedSlots).filter(value => footerButtonSlots.includes(value))
      },
      getFooterSlotKey(key) {
        return key.replace('-bottom', '')
      },
      onFormResize({ width }) {
        this.showNav = width > 1500
      },
      onEdit() {
        if (!this.toolbarListeners.edit && hasOwn(this.$parent, 'disabled')) {
          this.$parent.disabled = false
        }
      },
      async navItemClick(item, index) {
        if (!this.showNav) {
          return
        }
        await this.$nextTick()
        this.activeFormItemGroup(item, index)
        if (!item.expand) {
          item.open()
        }
      },
      activeFormItemGroup(tab, index) {
        this.$refs.formScroller.scrollTop = tab.$el.offsetTop - 10
        animate(tab.$el, 'breathe-indicator-animation')
        this.tryByClick = true
        this.activeIndex = index
      },
      async tryActive() {
        if (!this.showNav) {
          return
        }
        for (let i = 0; i < this.groups.length; i++) {
          if (isInContainer(this.groups[i].$el, this.$refs.formScroller, { bottomOffset: 10 })) {
            this.activeIndex = i
            break
          }
        }
      },
      onFormScroll() {
        this.formScrollTop = this.$refs.formScroller.scrollTop
        this.formScrollLeft = this.$refs.formScroller.scrollLeft
        if (!this.tryByClick && this.groups.length) {
          window.requestAnimationFrame(this.tryActive)
        }
        this.tryByClick = false
      },
      scrollTo(top, left) {
        if (top) {
          this.$refs.formScroller.scrollTop = top
        }
        if (left) {
          this.$refs.formScroller.scrollLeft = left
        }
      },
      showFlowProgress() {
        flowProgressDialog({ props: { businessKey: this.$parent.businessKey } })
      },
      reloadForm() {
        this.$parent.initForm?.()
      }
    }
  }
</script>

<style lang="scss">
  .form-view {
    display: flex;
    flex-direction: column;
    height: 100%;

    .table-status-icon {
      font-size: 20px;
    }

    .form-scroller-wrapper {
      position: relative;
      flex: 1;
    }

    .not-form {
      margin: 0 auto;
    }

    .form-view-toolbar {
      overflow: hidden;
    }

    .form-scroller {
      flex: 1;
      overflow: auto;

      .el-row {
        position: static;
      }
    }

    .form-content {
      .el-form {
        .el-row {
          // padding-right: 2rem;
          .el-row {
            padding-right: 0;
          }
        }
      }
    }

    .form-card {
      @include box-shadow;

      // min-height: calc(100% - 28px);
      max-width: 1000px;
      margin: 14px auto;
      padding: 20px;
      color: #303133;
      background-color: #fff;
      border: 1px solid #ebeef5;
      border-radius: 4px;
      transition: 0.3s;
    }

    .form-nav {
      @include box-shadow;
      position: absolute;
      top: 55px;
      left: 20px;
      background-color: white;
    }

    .form-nav-list {
      min-width: 180px;
      padding: 10px 20px;
    }

    .form-nav-header {
      position: relative;
      padding: 5px 20px;
      color: var(--color-primary);

      // font-family: element-icons !important;
      font-weight: bold;
      font-size: 15px;
      background-color: #f3f5fb;

      // &::before {
      //  position: absolute;
      //  left: 5px;
      //  content: "\e71c";
      // }
    }

    .form-nav-item {
      position: relative;
      padding: 5px 0;
      font-family: element-icons !important;
      cursor: pointer;

      &.active,
      &:hover {
        color: var(--color-primary);
        font-weight: bold;
      }

      &.active {
        &::before {
          position: absolute;
          top: 8px;
          left: -14px;
          content: '\e6e0';
        }
      }
    }

    .form-view-title {
      width: 100%;
      height: 36px;

      // margin-top: -10px;
      margin-bottom: 10px;
      color: #333;
      font-size: 22px;
      line-height: 36px;
      text-align: center;
      background-color: #fff;
    }

    // TODO 把下面的样式移走
    .tip {
      margin: 20px 0;
      padding: 8px 16px;
      background-color: #ecf8ff;
      border-left: 5px solid #50bfff;
      border-radius: 4px;

      p {
        color: #5e6d82;
        font-size: 14px;
        line-height: 1.5em;
      }

      &.danger {
        background-color: #fde2e2;
        border-left-color: #f56c6c;

        p {
          color: #564646;
        }
      }

      &.warning {
        background-color: #f7efe2;
        border-left-color: #e6a23c;

        p {
          color: #5d564b;
        }
      }
    }

    .select-num {
      color: #898989;
      font-size: 12px;

      .num {
        font-size: 19px;
        font-family: impact, Constantia, Georgia;
      }
    }

    .el-table {
      .el-table__header th {
        color: #595959;
        font-weight: normal;
        background: #fff;
      }

      td {
        padding: 4px 0;

        // 内含过多弹窗型input时
        .cell {
          .el-input {
            .el-input__inner {
              padding-left: 4px;
            }

            &.el-input--prefix {
              .el-input__inner {
                padding-left: 30px;
              }
            }

            &.is-disabled {
              .el-input__suffix {
                display: none;
              }

              .el-input__inner {
                line-height: 1.6;
                white-space: normal;
                word-break: break-word;
                background: transparent;
                border: 0;
              }
            }

            &.is-disabled:not(.el-date-editor--date) {
              .el-input__inner {
                padding: 0 0 0 4px;
              }
            }
          }
        }
      }
    }
  }

  @media screen and (min-width: 1700px) {
    .form-view.form-view__lg {
      .form-card {
        position: relative;
        left: 60px;
      }
    }
  }
</style>